package com.collins.reflections;

import lombok.NoArgsConstructor;
import org.apache.log4j.Logger;
import com.collins.reflections.annos.MyClassTest;
import com.collins.reflections.annos.MyMethodTest;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("uncheck")
public class RunMethods {
    // a map storing all test class instance, ensure each one is singleton
    public static Map<String, Object> beanMap = new HashMap<>();
    public static Logger logger = Logger.getLogger(RunMethods.class);

    public static void main(String[] args) throws IOException {
        try {
            init(new String[]{"com.collins"});
            run();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void init(String[] packageNames) throws IOException {
        // TODO add all annotated class into beanMap
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        if (contextClassLoader != null) {
            for (String packageName : packageNames) {
                String path = packageName.replace('.', '/');
                Enumeration<URL> resources = contextClassLoader.getResources(path);
                while (resources.hasMoreElements()) {
                    URL resource = resources.nextElement();
                    File file = new File(resource.getFile());
                    annotateFile(file, packageName);
                }
                logger.info("now bean map contains:");
                for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
                    logger.info(entry.getKey() + ":" + entry.getValue());
                }
            }
        }
    }

    public static void annotateFile(File file, String packageName) throws IOException {
        if (file.exists()) {
            File[] files = file.listFiles();
            if (files != null) {
                for (File f : files) {
                    if (f.isDirectory()) {
                        String prefix = f.getName();
                        String packageNameWithPrefix = packageName + "." + prefix;
                        annotateFile(f, packageNameWithPrefix);
                    }
                    if (f.isFile() && f.getName().endsWith(".class")) {
                        String className = f.getName().substring(0, f.getName().length() - 6);
                        try {
                            // TODO extract annotation
                            Class<?> cls = Class.forName(packageName + "." + className);
                            logger.info(cls.getName() + " is scanned ...");
                            if (cls.isAnnotationPresent(MyClassTest.class)) {
                                Constructor<?> constructor = cls.getConstructor();
                                constructor.setAccessible(true);
                                Object o = constructor.newInstance();
                                beanMap.put(packageName + "." + className, o);
                                logger.info("[bean] " + packageName + "." + className + " is added...");
                            }
                        } catch (ClassNotFoundException | NoSuchMethodException |
                                 InvocationTargetException | InstantiationException |
                                 IllegalAccessException e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
    }

    public static void run() {
        // TODO fetch class in beanMap
        for (Map.Entry<String, Object> entry : beanMap.entrySet()) {
            // gain instance and fetch its class
            Object o = entry.getValue();
            Class<?> c = o.getClass();
            // TODO assert method is testable
            Method[] methods = c.getDeclaredMethods();
            for (Method method : methods) {
                method.setAccessible(true);
                if (method.isAnnotationPresent(MyMethodTest.class)) {
                    MyMethodTest ma = method.getAnnotation(MyMethodTest.class);
                    if (ma != null) {
                        System.out.println("[framework message]" + ma.value());
                    }
                    // invoke this method
                    try {
                        method.invoke(o);
                    } catch (InvocationTargetException | IllegalAccessException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        }
    }
}

@SuppressWarnings("uncheck")
class someClass {
    private static someClass instance;

    private someClass() {
    }

    public static someClass getInstance() {
        if (instance == null) {
            synchronized (someClass.class) {
                if (instance == null) {
                    instance = new someClass();
                }
            }
        }
        return instance;
    }

}

@MyClassTest("this is a override value")
@NoArgsConstructor
class otherClass {
    @MyMethodTest
    public void otherTest01() {
        System.out.println("otherClass - otherTest01");
    }

    @MyMethodTest("otherClass - override message ...")
    public void otherTest02() {
        System.out.println("otherClass - otherTest02");
    }
}

@MyClassTest
@NoArgsConstructor
class anotherClass {
    @MyMethodTest
    public void anotherTest01() {
        System.out.println("anotherClass - otherTest01");
    }

    @MyMethodTest
    public void anotherTest02() {
        System.out.println("anotherClass - otherTest02");
    }

    @MyMethodTest("anotherClass - override message ...")
    public void anotherTest03() {
        System.out.println("anotherClass - otherTest03");
    }
}